//
// Scheduler.cpp
// Usage: Process scheduler
// CreateDate: 2009-04-20
// Author: CaiRuicheng
//
#include <NewLife.h>
#include <stdlib.h>
#include "Scheduler.h"
#include "Process.h"
#include "Thread.h"
#include "../intrman/ClockIRQAction.h"

CScheduler::ScheduleCallout * CScheduler::ScheduleCallout::instance = NULL;

CScheduler::CScheduler()
{
	timeout(ScheduleCallout::GetInstance(this, 10));

	// Create resouce
	Resource * res = new Resource(this);
	ResourceManager.CreateResource(
		res,
		ServiceCallInterface::Scheduler::ResourceName
	);
	
	// Register interface
	res->RegisterInterface(
		(Resource::CallInterface0)&CScheduler::Schedule,
		ServiceCallInterface::Scheduler::SCIID_Schedule
	);
	res->RegisterInterface(
		(Resource::CallInterface1)&CScheduler::AddScheduleObject,
		ServiceCallInterface::Scheduler::SCIID_AddScheduleObject
	);
	res->RegisterInterface(
		(Resource::CallInterface1)&CScheduler::RemoveScheduleObject,
		ServiceCallInterface::Scheduler::SCIID_RemoveScheduleObject
	);
	res->RegisterInterface(
		(Resource::CallInterface1)&CScheduler::Block,
		ServiceCallInterface::Scheduler::SCIID_Block
	);
	res->RegisterInterface(
		(Resource::CallInterface1)&CScheduler::WakeUp,
		ServiceCallInterface::Scheduler::SCIID_WakeUp
	);
	res->RegisterInterface(
		(Resource::CallInterface1)&CScheduler::SelectScheduleObject,
		ServiceCallInterface::Scheduler::SCIID_SelectScheduleObject
	);
}

CScheduler::~CScheduler()
{
	untimeout(ScheduleCallout::GetInstance());
}


void CScheduler::AddScheduleObject(ScheduleObject * so)
{
	int queue = so->GetPriority();
	if (queue>0 && queue<4)
	{
		queue = 1;
	}
	if (so->GetProcessStatus()==PS_BLOCKED)
		queue = SCHEDULE_QUEUE_SIZE-1;
	if(queue >= SCHEDULE_QUEUE_SIZE)
		panic("CScheduler::AddScheduleObject -> Schedule queue out of range.");	

	LinkedList<ScheduleObject *> &SQ = m_ScheduleQueue[queue];
	SQ.insertLast(so);
}
//delete ready process then zhijie RemoveScheduleObject,but delete Blocked process ,it is sure that it's state is PS_BLOCKED.
void CScheduler::RemoveScheduleObject(ScheduleObject * so)
{
	int queue = so->GetPriority();
	int pid =  so->GetPID();

	if (queue>0 && queue<4)
	{
		queue = 1;
	}
	if (so->GetProcessStatus()==PS_BLOCKED)
		queue = SCHEDULE_QUEUE_SIZE-1;
	if(queue >= SCHEDULE_QUEUE_SIZE)
		panic("CScheduler::AddScheduleObject -> Schedule queue out of range.");	
	
	m_ScheduleQueue[queue].iterateFirst();
	while(m_ScheduleQueue[queue].iterateIsValid())
	{
		if(((Process *)m_ScheduleQueue[queue].iterateGetValue())->GetPID() == pid)
		{
			m_ScheduleQueue[queue].remove();
			break;
		}
		m_ScheduleQueue[queue].iterateNext();
	}
}

void CScheduler::WakeUp(pid_t pid)
{
	Resource * res = ResourceManager.RequestResource(
			ServiceCallInterface::ProcessManager::ResourceName
		);
	Process * proc = (Process *)res->CallSCI(
			ServiceCallInterface::ProcessManager::SCIID_GetProcessByPid,
			pid
		);
	if (proc==NULL)
	{
		panic("Try to WakeUp unexist process! pid %d.", pid);
	}
	else
	{
		if (proc->GetProcessStatus() != PS_BLOCKED)
			panic("Trying to WakeUp the process is not BLOCKED! pid %d.", pid);
			
	}

/*
	int queue = SCHEDULE_QUEUE_SIZE-1;

	m_ScheduleQueue[queue].iterateFirst();
	while(m_ScheduleQueue[queue].iterateIsValid())
	{
		if(((Process *)m_ScheduleQueue[queue].iterateGetValue())->GetPID() == pid)
		{
			m_ScheduleQueue[queue].remove();
			break;
		}
		m_ScheduleQueue[queue].iterateNext();
	}
*/
	RemoveScheduleObject(proc);
	proc->SetProcessStatus(PS_READY);
/*
	int iPriority = proc->GetPriority();
	int q = iPriority;

	if (iPriority>0 && iPriority<4)
	{
		q=1;
	}
	
	m_ScheduleQueue[q].insertLast(proc);
*/
	AddScheduleObject(proc);
	return;
}

//bu neng yong RemoveScheduleObject()
void CScheduler::Block(pid_t pid)
{
	Resource * res = ResourceManager.RequestResource(
		ServiceCallInterface::ProcessManager::ResourceName
	);
	Process * proc = (Process *)res->CallSCI(
		ServiceCallInterface::ProcessManager::SCIID_GetProcessByPid,
		pid
	);

	if(proc==NULL)
		panic("Try to block unexist process! pid %d.", pid);
	

	//	
	if (pid == GetCurrentProcess()->GetPID())
	{
		proc->SetProcessStatus(PS_BLOCKED);
		Schedule();
	}
	else
	{
/*
		int iPriority = proc->GetPriority();
		int queue = iPriority;
	
		if (iPriority>0 && iPriority<4)
		{
			queue=1;
		}
		
		m_ScheduleQueue[queue].iterateFirst();
		while(m_ScheduleQueue[queue].iterateIsValid())
		{
			if(((Process *)m_ScheduleQueue[queue].iterateGetValue())->GetPID() == pid)
			{
				m_ScheduleQueue[queue].remove();
				break;
			}
			m_ScheduleQueue[queue].iterateNext();
		}
*/
		RemoveScheduleObject(proc);
		proc->SetProcessStatus(PS_BLOCKED);	
		AddScheduleObject(proc);
	}

	return;
}


/*
process schedule have three steps:
   1.process select
   2.modify ebp because linuix STF
   3.switch TSS
*/
void CScheduler::Schedule()
{	
	LinkedList<ScheduleObject *> *lstCurrent = NULL;
	ScheduleObject *curSO = GetCurrentProcess();
	
	// If current process have tick and it status is creating, running or
	// ready, just return. Else do schedule.
	if (curSO->GoOn() && curSO->GetProcessStatus() < PS_BLOCKED)
		return;

	int i;
	int iPriority = curSO->GetPriority();
	int queue=iPriority;

	if(queue>=1 && queue<=3)
		queue = 1;
	else if (queue>3)
		queue -= 2;

	//qu chu youxianji panduan ,if ==0,zezhijie insert queue
	//if usr ze jiangdiyouxianji ,if min ze shengzhi max
	//qita youxianji bubian

	if (curSO->GetProcessStatus()==PS_BLOCKED)
	{
		m_ScheduleQueue[block].insertLast(curSO);
	}
	else
	{
	/*
	   If process priority is 0, insert into int schedule queue.
	   If is usr priority(1-3), decrease priority, set priority to 1
	     when decrease to lowest priority.
	   Other process's priority stay unchanged.
	*/
	if (iPriority>=1 && iPriority<=3)
	{
		m_ScheduleQueue[queue].iterateFirst();

		for (i=1;i<iPriority;i++)
		{
			if (!m_ScheduleQueue[queue].iterateIsValid())
			{
				m_ScheduleQueue[queue].insertLast(curSO);
				break;
			}
			m_ScheduleQueue[queue].iterateNext();
		}
		if (i==iPriority)
		{
			if(m_ScheduleQueue[queue].iterateGetValue()==NULL)
				m_ScheduleQueue[queue].insertLast(curSO);
			else
				m_ScheduleQueue[queue].iterateInsertAfter(curSO);
		}
	}
	else
	{
	/*
		if (curSO->GetProcessStatus()==PS_BLOCKED)
		{
			m_ScheduleQueue[block].insertLast(curSO);
		}
		else
	*/
		if (curSO->GetProcessStatus()==PS_DEAD)
		{
			
		}
		else
		{
			m_ScheduleQueue[queue].insertLast(curSO);
		}
	}	
	}


	// Search from high priority queue to low priority queue, find 
	// an unempty queue


	for (i=0; i<SCHEDULE_QUEUE_SIZE-1; i++)
	{
		if(m_ScheduleQueue[i].getLength()!=0)
		{
			lstCurrent = &m_ScheduleQueue[i];
			break;
		}
	}
	
	// If no such queue, panic kernel
	if (i==SCHEDULE_QUEUE_SIZE-1)
	{
		panic("KNL: No process to schedule.(schedule/Scheduler.cpp)\n");
	}

	SwitchProcess(GetCurrentProcess(), lstCurrent->removeFirst());

/*
	// Get source process kernel stack information
	THREAD_BLOCK *sourceTCB = curSO->GetThread()->GetTCB();
 	u32 sourceBP = (u32)sourceTCB;
	u32 sourceTop = sourceBP + 2*PAGE_SIZE;
	u32 srccurBP;
	u32 sourceSP;
	u32 src_ebp;

	__asm__ __volatile__(
		"movl %%esp, %0\t\n"
		"movl %%ebp, %1"
		:"=m"(sourceSP),"=m"(srccurBP)
	);

	u32 msize = sourceTop-sourceSP;

	// Get target processs kernel stack information
	curSO = lstCurrent->removeFirst();
	THREAD_BLOCK *destTCB = curSO->GetThread()->GetTCB();
 	u32 destBP = (u32)destTCB;
	u32 destSP=destBP+sourceSP-sourceBP;
	//from low to high copy
	memcpy((void *)destSP,(void *)sourceSP,msize);


	// fix stack retaddr
	u32 *p = (u32 *)srccurBP;

	while( *p>sourceSP && *p<sourceSP+2*PAGE_SIZE )
	{
		u32 * target = (u32*)((u32)p-(u32)sourceTCB+(u32)destTCB) ;
		*target = *p - (u32)sourceTCB + (u32)destTCB;
		p = (u32 *)*p;
	}

	// Set ebp register to target process's ebp
	__asm__ __volatile__("movl %0, %%ebp"::"r"(srccurBP-(u32)sourceTCB + (u32)destTCB));
		
	// Switch context to target process
	curSO->SwitchContext();

	// Set esp0 field in TSS
	tss.esp0 = (u32)(&destTCB->regs.ss) + 4;
*/
}

void CScheduler::SelectScheduleObject(ScheduleObject * target)
{
	static const char * strProcessStatus[] = {
		"PS_CREATING", "PS_RUNNING", "PS_READY", "PS_BLOCKED", "PS_DEAD"
	};
	// if selected object is not ready to run, it's a kerne fault
	if(target->GetProcessStatus()>=PS_BLOCKED)
	{
		panic("Selected object is not ready to run!\n"
		      "CurrentProcess PID%d.\n"
		      "SelectedProcess PID%d, %s",
		      GetCurrentProcess()->GetPID(),
		      target->GetPID(),
		      strProcessStatus[target->GetProcessStatus()]
		);
	}

	ScheduleObject * current = GetCurrentProcess();
	if(target != current);
	{
		// Add current process to queue
		int queue = current->GetPriority();
		if(queue>=1 && queue<=3)
			queue = 1;
		else if(queue>3)
			queue -= 2;
		m_ScheduleQueue[queue].insertFirst(current);

		// Remove target from queue
		queue = target->GetPriority();
		if(queue>=1 && queue<=3)
			queue = 1;
		else if(queue>3)
			queue -= 2;
		m_ScheduleQueue[queue].iterateFirst();
		while(m_ScheduleQueue[queue].iterateIsValid())
		{
			if(m_ScheduleQueue[queue].iterateGetValue() == target)
				break;
			m_ScheduleQueue[queue].iterateNext();
		} 

		// Can't find target process in the schedule queue, panic kernel
		if(!m_ScheduleQueue[queue].iterateIsValid())
			panic("Can't switch process: No such process.");

		m_ScheduleQueue[queue].remove();

		// Switch to target process 
		SwitchProcess(current, target);
	}
}

void CScheduler::SwitchProcess(ScheduleObject * current, ScheduleObject * target)
{
	// Get current process kernel stack information
	THREAD_BLOCK *currentTCB = current->GetThread()->GetTCB();
 	u32 curTCB_Base = (u32)currentTCB;
	u32 curTCB_Top = curTCB_Base + 2*PAGE_SIZE;
	u32 curESP;
	u32 curEBP;
	__asm__ __volatile__(
		"movl %%esp, %0\t\n"
		"movl %%ebp, %1"
		:"=m"(curESP),"=m"(curEBP)
	);
	u32 StkSize = curTCB_Top - curESP;

	// Get target processs kernel stack information
	THREAD_BLOCK *targetTCB = target->GetThread()->GetTCB();
 	u32 targetTCB_Base = (u32)targetTCB;
	u32 targetESP = targetTCB_Base + curESP - curTCB_Base;

	//from low to high copy kernel stack
	memcpy((void *)targetESP, (void *)curESP, StkSize);

	// fix stack retaddr
	u32 *p = (u32 *)curEBP;

	while( *p>curESP && *p<curESP+2*PAGE_SIZE )
	{
		u32 * target = (u32*)((u32)p - curTCB_Base + targetTCB_Base);
		*target = *p - curTCB_Base + targetTCB_Base;
		p = (u32 *)*p;
	}

	// Set ebp register to target process's ebp
	__asm__ __volatile__("movl %0, %%ebp"::"r"(curEBP - curTCB_Base + targetTCB_Base));
	
	// Switch context to target process
	target->SwitchContext();

/*
	kprintf("[%d]", target->GetPID());
	kprintf("{{");
	for(int i=0; i<SCHEDULE_QUEUE_SIZE; i++)
	{
		m_ScheduleQueue[i].iterateFirst();
		while(m_ScheduleQueue[i].iterateIsValid())
		{
			kprintf("%d ", m_ScheduleQueue[i].iterateGetValue()->GetPID());
			m_ScheduleQueue[i].iterateNext();
		}
		kprintf(",");
	}
	kprintf("}}");
*/

	// Set esp0 field in TSS
	tss.esp0 = (u32)(&targetTCB->regs.ss) + 4;
}

